//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#ifndef NJ_ATOMIC_BASE_LINUX_INLINE_H
#	define NJ_ATOMIC_BASE_LINX_INLINE_H

#	if NJ_COMPILER_VERSION<40100
#		define NJ_BIT_DESTINATION "=m"
#	else // NJ_COMPILER_VERSION<40100
#		define NJ_BIT_DESTINATION "+m"
#	endif // NJ_COMPILER_VERSION<40100

	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::NjAtomicBase()
	{
#	if NJ_ASSERT_ENABLED
		TestAlignment();
#	endif // NJ_ASSERT_ENABLED
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::NjAtomicBase(NjAtomicBase const & kCopy)
	:
		m_iNumber(kCopy.m_iNumber)
	{
#	if NJ_ASSERT_ENABLED
		TestAlignment();
#	endif // NJ_ASSERT_ENABLED
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::NjAtomicBase(Number iCopy)
	:
		m_iNumber(iCopy)
	{
#	if NJ_ASSERT_ENABLED
		TestAlignment();
#	endif // NJ_ASSERT_ENABLED
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::~NjAtomicBase()
	{
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<4>::Increment()
	{
		Add(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PreIncrement()
	{
		return PreAdd(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PostIncrement()
	{
		return PostAdd(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<4>::Decrement()
	{
		Sub(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PreDecrement()
	{
		return PreSub(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PostDecrement()
	{
		return PostSub(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<4>::Add(Number iRightValue)
	{
		PostAdd(iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PreAdd(Number iRightValue)
	{
		return __sync_add_and_fetch(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PostAdd(Number iRightValue)
	{
		return __sync_fetch_and_add(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<4>::Sub(Number iRightValue)
	{
		PostSub(iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PreSub(Number iRightValue)
	{
		return __sync_sub_and_fetch(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PostSub(Number iRightValue)
	{
		return __sync_fetch_and_sub(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PostOr(Number iRightValue)
	{
		return __sync_fetch_and_or(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PostAnd(Number iRightValue)
	{
		return __sync_fetch_and_and(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PostXor(Number iRightValue)
	{
		return __sync_fetch_and_xor(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PostExchange(Number iValue)
	{
#	if NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64
		asm volatile(	"lock xchg %0, %1\n"
		: 		"=r" (iValue), NJ_BIT_DESTINATION (m_iNumber)
		:		"0" (iValue)
		:		"memory");
		
		return iValue;
#	elif NJ_PLATFORM_ASM_ARM_V6K
		register Number iResult;
		register Number volatile * const pDestination(&m_iNumber);
		asm volatile(	"l%=: ldrex %0, [%1]\n"
				"strex r1, %2, [%1]\n"
				"cmp r1, #0\n"
				"bne l%="
		:		"=&r" (iResult)
		:		"r" (pDestination), "r" (iValue)
		:		"r1");
		
		return iResult;
#	else  // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
#		error "Unsupported build configuration"
#	endif // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::PostCompareExchange(Number iComparand, Number iValue)
	{
		return __sync_val_compare_and_swap(&m_iNumber, iComparand, iValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjBool NjAtomicBase<4>::PostSetBit(Number iBit)
	{
#	if NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64
		Number iResult;
		
		asm volatile(	"lock bts %2, %1\n"
				"sbb %0, %0" 
		: 		"=r" (iResult), NJ_BIT_DESTINATION (m_iNumber)
		:		"Ir" (iBit)
		:		"memory");
		
		return iResult;
#	elif NJ_PLATFORM_ASM_ARM_V6K
		iBit=Number(1)<<iBit;
		return (PostOr(iBit)&iBit)!=0;
#	else  // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
#		error "Unsupported build configuration"
#	endif // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjBool NjAtomicBase<4>::PostResetBit(Number iBit)
	{
#	if NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64
		Number iResult;
		
		asm volatile(	"lock btr %2, %1\n"
				"sbb %0, %0" 
		: 		"=r" (iResult), NJ_BIT_DESTINATION (m_iNumber)
		:		"Ir" (iBit)
		:		"memory");
		
		return iResult;
#	elif NJ_PLATFORM_ASM_ARM_V6K
		iBit=Number(1)<<iBit;
		return (PostAnd(~iBit)&iBit)!=0;
#	else  // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
#		error "Unsupported build configuration"
#	endif // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<4>::Number NjAtomicBase<4>::Get() const
	{
		return m_iNumber;
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<4>::Set(Number iValue)
	{
		m_iNumber=iValue;
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::NjAtomicBase()
	{
#	if NJ_ASSERT_ENABLED
		TestAlignment();
#	endif // NJ_ASSERT_ENABLED
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::NjAtomicBase(NjAtomicBase const & kCopy)
	:
		m_iNumber(kCopy.m_iNumber)
	{
#	if NJ_ASSERT_ENABLED
		TestAlignment();
#	endif // NJ_ASSERT_ENABLED
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::NjAtomicBase(Number iCopy)
	:
		m_iNumber(iCopy)
	{
#	if NJ_ASSERT_ENABLED
		TestAlignment();
#	endif // NJ_ASSERT_ENABLED
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::~NjAtomicBase()
	{
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<8>::Increment()
	{
		Add(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PreIncrement()
	{
		return PreAdd(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PostIncrement()
	{
		return PostAdd(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<8>::Decrement()
	{
		Sub(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PreDecrement()
	{
		return PreSub(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PostDecrement()
	{
		return PostSub(1);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<8>::Add(Number iRightValue)
	{
		PostAdd(iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PreAdd(Number iRightValue)
	{
		return __sync_add_and_fetch(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PostAdd(Number iRightValue)
	{
		return __sync_fetch_and_add(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<8>::Sub(Number iRightValue)
	{
		PostSub(iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PreSub(Number iRightValue)
	{
		return __sync_sub_and_fetch(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PostSub(Number iRightValue)
	{
		return __sync_fetch_and_sub(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PostOr(Number iRightValue)
	{
		return __sync_fetch_and_or(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PostAnd(Number iRightValue)
	{
		return __sync_fetch_and_and(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PostXor(Number iRightValue)
	{
		return __sync_fetch_and_xor(&m_iNumber, iRightValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PostExchange(Number iValue)
	{
#	if NJ_PLATFORM_ASM_INTEL_X86
		Number iResult(m_iNumber);
		
		for(;;)
		{
			Number const iOldValue(PostCompareExchange(iResult, iValue));
			
			if (iOldValue==iResult)
				return iResult;
				
			iResult=iOldValue;
		}
#	elif NJ_PLATFORM_ASM_INTEL_X64
		asm volatile(	"lock xchg %0, %1\n"
		: 		"=r" (iValue), NJ_BIT_DESTINATION (m_iNumber)
		:		"0" (iValue)
		:		"memory");
		
		return iValue;
#	elif NJ_PLATFORM_ASM_ARM_V6K
		register Number iResult;
		register Number volatile * const pDestination(&m_iNumber);
		asm volatile(	"l%=: ldrexd %0, [%1]\n"
				"strexd r1, %2, [%1]\n"
				"cmp r1, #0\n"
				"bne l%="
		:		"=&r" (iResult)
		:		"r" (pDestination), "r" (iValue)
		:		"r1");
		
		return iResult;
#	else  // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
#		error "Unsupported build configuration"
#	endif // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::PostCompareExchange(Number iComparand, Number iValue)
	{
		return __sync_val_compare_and_swap(&m_iNumber, iComparand, iValue);
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjBool NjAtomicBase<8>::PostSetBit(Number iBit)
	{
#	if NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64
#		if NJ_PLATFORM_ASM_INTEL_X86
		NJ_COMPILE_ASSERT(NJ_PLATFORM_LITTLE_ENDIAN);

		volatile NjInt32 * pDest(reinterpret_cast<volatile NjInt32 *>(&m_iNumber));
		
		if (iBit>32)
		{
			iBit-=32;
			++pDest;
		}
		
		typedef NjInt32 OpNumber;
#		else // NJ_PLATFORM_ASM_INTEL_X86
		volatile Number * const pDest(&m_iNumber);
		
		typedef Number OpNumber;
#		endif // NJ_PLATFORM_ASM_INTEL_X86

		OpNumber iResult;
		
		asm volatile(	"lock bts %2, %1\n"
				"sbb %0, %0" 
		: 		"=r" (iResult), NJ_BIT_DESTINATION (*pDest)
		:		"Ir" (OpNumber(iBit))
		:		"memory");

		return iResult!=0;
#	elif NJ_PLATFORM_ASM_ARM_V6K
		iBit=Number(1)<<iBit;
		return (PostOr(iBit)&iBit)!=0;
#	else  // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
#		error "Unsupported build configuration"
#	endif // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjBool NjAtomicBase<8>::PostResetBit(Number iBit)
	{
#	if NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64
#		if NJ_PLATFORM_ASM_INTEL_X86
		NJ_COMPILE_ASSERT(NJ_PLATFORM_LITTLE_ENDIAN);

		volatile NjInt32 * pDest(reinterpret_cast<volatile NjInt32 *>(&m_iNumber));
		
		if (iBit>32)
		{
			iBit-=32;
			++pDest;
		}
		
		typedef NjInt32 OpNumber;
#		else //  NJ_PLATFORM_ASM_INTEL_X86
		volatile Number * const pDest(&m_iNumber);
		
		typedef Number OpNumber;
#		endif // NJ_PLATFORM_ASM_INTEL_X86

		OpNumber iResult;
		
		asm volatile(	"lock btr %2, %1\n"
				"sbb %0, %0" 
		: 		"=r" (iResult), NJ_BIT_DESTINATION (*pDest)
		:		"Ir" (OpNumber(iBit))
		:		"memory");
		
		return iResult!=0;
#	elif NJ_PLATFORM_ASM_ARM_V6K
		iBit=Number(1)<<iBit;
		return (PostAnd(~iBit)&iBit)!=0;
#	else  // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
#		error "Unsupported build configuration"
#	endif // NJ_PLATFORM_ASM_INTEL_X86 || NJ_PLATFORM_ASM_INTEL_X64 || NJ_PLATFORM_ASM_ARM_V6K
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<8>::Number NjAtomicBase<8>::Get() const
	{
#	if NJ_PLATFORM_32_BIT
		return const_cast<NjAtomicBase *>(this)->PostCompareExchange(0, 0);
#	else //  NJ_PLATFORM_32_BIT
		return m_iNumber;
#	endif //  NJ_PLATFORM_32_BIT
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE void NjAtomicBase<8>::Set(Number iValue)
	{
#	if NJ_PLATFORM_32_BIT
		PostExchange(iValue);
#	else //  NJ_PLATFORM_32_BIT
		m_iNumber=iValue;
#	endif //  NJ_PLATFORM_32_BIT
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<1>::Number NjAtomicBase<1>::PostCompareExchange(Number iComparand, Number iValue)
	{
		NJ_COMPILE_ASSERT(NJ_PLATFORM_LITTLE_ENDIAN);

		return __sync_val_compare_and_swap(reinterpret_cast<volatile NjInt8 *>(&m_iNumber), static_cast<NjInt8>(iComparand), static_cast<NjInt8>(iValue));
	}
	//----------------------------------------------------------------------------
	NJ_FORCE_INLINE NjAtomicBase<2>::Number NjAtomicBase<2>::PostCompareExchange(Number iComparand, Number iValue)
	{
		NJ_COMPILE_ASSERT(NJ_PLATFORM_LITTLE_ENDIAN);

		return __sync_val_compare_and_swap(reinterpret_cast<volatile NjInt16 *>(&m_iNumber), static_cast<NjInt16>(iComparand), static_cast<NjInt16>(iValue));
	}
	//----------------------------------------------------------------------------

#undef NJ_BIT_DESTINATION

#endif // NJ_ATOMIC_BASE_LINUX_INLINE_H

